/* MODULO : BLOCKENCRYPTER.C
*  =========================
*  Archivo : blockEncrypter.c
*  Implementacion del modulo principal de la aplicacion.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "blockEncrypter.h"
#include "util.h"

/* Definiciones de utileria */
#define FERROR(label, errorCode) 	{\
										fprintf(stderr, label);\
										return errorCode;\
									}
#define UNSET -1
#define TRUE 1
#define FALSE 0

/* Definicion  de Codigos de comando para el parser */
char *validSwitches[] = {"-in","-out", "-e", "-d", "-pass", "-a", "-m", "-K", "-iv" }; 
enum {INPUT, OUTPUT, ENCRYPT, DECRYPT, PASS, ALG_TYPE, MODE, KEY, IV};
enum{ AES, DES };
enum{ ECB, CFB, OFB, CBC, CFB1, CFB8 };

/*Definicion de tamanos de bloque para las dos primitivas*/
#define AES_BLOCK_SIZE 128
#define DES_BLOCK_SIZE 64

/*
 * Con estas cadenas, puedo usar EVP_get_cipherbyname("aes-128-cfb" ) para obtener
 * un objeto EVP_CIPHER necesario para setear el tipo de encriptado
 */

const char *des_modes_EVP_string[] = { "des-ecb", "des-cfb","des-ofb", "des-cbc" };
const char *aes_modes_EVP_string[] = { "aes-128-ecb", "aes-128-cfb", "aes-128-ofb", "aes-128-cbc", "aes-128-cfb1", "aes-128-cfb8" };

/* 
*  DEFINICION DE FUNCIONES ESTATICAS INTERNAS AL MODULO
*  ====================================================
*/

/* Funciones para parsear la entrada de datos*/
static int ParseCommandLine( int cant, char *args[], FILE **input,FILE **output,
							 char **outputFilename, operationT *operation);
static int GetCommandCode( char *cad );

/* Funciones auxiliares para validar los argumentos de la linea de comandos */
static char *strToUpper( char *cad );
static int isValidMode(char *str, int *modeCode);
static int isValidFormat( char *str, int algorithm );
static int isBMPfile( char *string );
//static void printHexa(unsigned char *buf, int len);


/* Funciones para manejar la configuracion de una operacion, y ejecutarla */
static void setDefaultValues( operationT *operation );
void ShowOperationData( blockEncrypterT b );
static void freeOperation( operationT *operation );
static int executeOperation( operationT operation, imageADT image );
static const char *getEncryptionModeString( operationT operation );

/*
*  IMPLEMENTACION DE LAS FUNCIONES PUBLICAS
*  ========================================
*/

blockEncrypterT 
InitializeBlockEncrypter( int cant, char *args[] )
{
	int errorCode;
	blockEncrypterT processor;


	/* Reservamos memoria para la estructura principal del parser */
	if( (processor=malloc(sizeof(struct blockEncrypter))) == NULL )
	    	FERROR("\nInitializeBlockEncrypter(): Memory Error for main\
			structure\n", NULL)

	/* Si no se agregan los algoritmos, EVP_get_cipherbyname() no puede
	 * hacer lookup por cadenas tipo "aes-128-ecb" */
	OpenSSL_add_all_algorithms();
	
	if( (processor->operation=malloc(sizeof(struct operation))) == NULL )
	    	FERROR("\nInitializeBlockEncrypter(): Memory Error for operation\
			structure\n", NULL)
	setDefaultValues(&(processor->operation));

	/* Creamos una estructura para guardar la info de la imagen*/
	if( newImage( &processor->image) != 0 )
	{		
		free( processor->operation);
		free(processor);
		FERROR("\nInitializeBlockEncrypter(): Memory Error for the Image\n", NULL)
	}

	/* Inicialmente estos handles deben estar en NULL */
	processor->input = processor->output = NULL; 
	processor->outputFilename = NULL;
	

	/* Parseo los comandos de linea, lleno datos de IO y tipo de operacions*/
	if( ParseCommandLine( cant, args, &processor->input, &processor->output,
		      &processor->outputFilename, &processor->operation) != 0 ) 
	{		
		if( processor->outputFilename != NULL )
			remove( processor->outputFilename );
		TerminateBlockEncrypter(&processor);
		FERROR("\nInitializeBlockEncrypter(): Invalid command line\n", NULL)
	}

	/* Cargo la imagen desde archivo a memoria */
	int blockSize = (processor->operation->algorithm == AES)?AES_BLOCK_SIZE/8 : DES_BLOCK_SIZE/8;
	if( (errorCode=loadImage( &processor->image, processor->input, blockSize )) != 0 )
	{
		if( processor->outputFilename != NULL )
			remove( processor->outputFilename );
		TerminateBlockEncrypter(&processor);
		//printf("\nInitalizeImageProcessor(): ErrorCode = %d\n", errorCode);
		FERROR("\nInitalizeImageProcessor(): Unable to load image\n", NULL )
	}
	
	return processor; 
}

int 
RunBlockEncrypter( blockEncrypterT processor )
{
	int errorCode;

	if( processor == NULL )
		FERROR("RunBlockEncrypter(): Invalid processor\n", 1)
	
	/* Aplico la operacion indicada por linea de comando */
	if( (errorCode = executeOperation( processor->operation, processor->image)) != 0 )
	{
		printf("RunBlockEncrypter(): Error Code = %d\n", errorCode);
		FERROR("RunBlockEncrypter(): Error while executing command.\n", 2)
	}	

	/* Si fue exitoso, salvo el resultado */
	if( processor->output != NULL )
	{
		if( saveImage(&processor->image, processor->output) != 0 )
			FERROR("RunBlockEncrypter(): Unable to save image\n", 3)
	}else{
		printf("RunBLockEncrypter(): output is NULL\n");
	}
		
	return 0;
}

void 
TerminateBlockEncrypter( blockEncrypterT *processor )
{
	
	if( processor == NULL )
		return;
	if( *processor != NULL )
	{
		/* Cierro los archivos */
		if( (*processor)->output != NULL )
			fclose( (*processor)->output );
		if( (*processor)->input != NULL )
			fclose( (*processor)->input );
		
		/* Libero la imagen guardada en memoria*/
		freeImage( &(*processor)->image );
		
		/* Libero memoria para la copia del nombre del 
		 * archivo de salida */
		if( (*processor)->outputFilename != NULL )
			free( (*processor)->outputFilename );
		
		/* Libero la estructura para la info de operacion y 
		 * la estructura principal */
		freeOperation( &((*processor)->operation));	
		free( *processor );
		*processor = NULL;
	}
	EVP_cleanup();
	return;
}

static int
executeOperation( operationT operation, imageADT image)
{
	int errorCode;
	const char *encryptionModeString;

	if(  operation == NULL || image == NULL )
		FERROR("executeOperation(): Invalid operation or invalid image object\n", 1)

	encryptionModeString = getEncryptionModeString( operation );

	switch( operation->type)
	{
    	case ENCRYPT: 	if( (errorCode=encryptImage( &image,  encryptionModeString, (unsigned char*)operation->key, (unsigned char*)operation->iv)) != 0 )
    					{
    						printf("executeOperation(): Error Code %d\n", errorCode);
    						FERROR("executeOperation(): Encryption error\n", 2)
    					}							
						break;
		case DECRYPT:	if( (errorCode=decryptImage( &image, encryptionModeString, (unsigned char*)operation->key, (unsigned char*)operation->iv)) != 0 )
						{
							printf("executeOperation(): Error Code %d\n", errorCode);
							FERROR("executeOperation(): Decryption error\n", 3)
						}											
						break;
		default:		FERROR("executeOperation(): Unknown operation\n", 4)
	}

	return 0;
}

static const char *
getEncryptionModeString( operationT operation )
{
	const char *encryptionModeString;
	if( operation->algorithm == AES )
		encryptionModeString =  aes_modes_EVP_string[operation->mode];
	else
		encryptionModeString = des_modes_EVP_string[operation->mode];
	return encryptionModeString;
}

/*
*  IMPLEMENTACION DE FUNCIONES ESTATICAS INTERNAS
*  ==============================================
*/

static int 
ParseCommandLine( int cant, char *args[], FILE **input, FILE **output, 
							char **outputFilename, operationT *operation )
{	
	int i, size, len, ic, commandCode, modeCode;
	char *algName, *mode;

	for( i=0 ; i<cant ; i++ )
	{			
		commandCode = GetCommandCode(args[i]);

		switch( commandCode )
		{
		   case INPUT:	if( isBMPfile(args[i+1]) )
		   				{
		   					if( ( *input=fopen(args[i+1], "rb")) == NULL )
								FERROR("\nParseCommandLine(): Unable to open input file\n", 1)	
		   				}
		   				else
		   					FERROR("\nParseCommandLine(): input file is not a BMP file \n", 2)
		   				i++;	
		   				break;
			case OUTPUT:if( isBMPfile(args[i+1]) )
		   				{
		   					if( (*output=fopen(args[i+1], "wb")) == NULL )
								FERROR("\nParseCommandLine(): Unable to open output file\n", 3)	
		   				}
		   				else
		   					FERROR("\nParseCommandLine(): output file is not a BMP file \n", 4)
		   				*outputFilename = strdup(args[i+1]);
		   				i++;
		   				break;		   					
			case ENCRYPT:	(*operation)->type = ENCRYPT;break;
			case DECRYPT:   (*operation)->type = DECRYPT;break;
			case PASS:		if( strlen( (*operation)->key ) > 0  )
								FERROR("\nParseCommandLine(): Key already set. Password can not be used \n", 5)
							(*operation)->password = strdup(args[++i]);
							len = strlen( (*operation)->password );
							ic = 1;
							size = ((*operation)->algorithm == AES)?AES_BLOCK_SIZE/8 : DES_BLOCK_SIZE/8;

							const char * encModeStr = getEncryptionModeString(*operation);
							//EVP_md5 (); EVP_sha1();EVP_get_cipherbyname(encModeStr);EVP_des_ecb()

							EVP_BytesToKey(	EVP_get_cipherbyname(encModeStr), EVP_md5 (), NULL,
											(unsigned char *)(*operation)->password, len, ic,
											(unsigned char *)(*operation)->key, (unsigned char *)(*operation)->iv);
							break;
			case ALG_TYPE:	algName = strToUpper(args[i+1]); 
							if( strcmp( algName, "AES") != 0 && strcmp( algName, "DES" ) != 0 )
							{
								free(algName);
							 	FERROR("\nParseCommandLine(): Algorithm must be \"aes\" or \"des\" \n", 6)
							}							
							(*operation)->algorithm = (strcmp(algName, "AES") == 0)?AES:DES;
							free(algName);
							i++;
							break;
			case MODE:	mode = strToUpper(args[i+1]);
						if( !isValidMode(mode, &modeCode) )					
		   				{
							free(mode);
						 	FERROR("\nParseCommandLine(): mode must be  \"ECB\", \"CFB\", \"OFB\", \"CBC\"\n", 7)
						}
						(*operation)->mode = modeCode;
						free(mode);
						i++;
						break;
			case KEY: 	if( (*operation)->password != NULL )
							FERROR("\nParseCommandLine(): Password already set. Key can not be used \n", 8)

						if( !isValidFormat( args[i+1],  (*operation)->algorithm) )
							FERROR("\nParseCommandLine(): Key must be hexa, length must be 8 bytes for DES or 16 bytes for AES\n", 9)
						len = strlen(args[i+1] );
						size = ((*operation)->algorithm == AES)?AES_BLOCK_SIZE/8 : DES_BLOCK_SIZE/8;

						/* Se  ingresa key en formato NO hexa */
						if ( ( (*operation)->algorithm == AES && len == 16 ) || ( (*operation)->algorithm == DES && len == 8 ) )
							strncpy( (*operation)->key, args[i+1], size);
						else
						{
							/*Cuando se ingresa key en formato Hexa*/
							hex2byte( (unsigned char *)(*operation)->key, args[i+1],size);
						}
						i++;
						break;
			case IV:	if( (*operation)->password != NULL )
							FERROR("\nParseCommandLine(): Password already set. IV can not be used \n", 10)
						if( !isValidFormat(args[i+1],  (*operation)->algorithm) )
							FERROR("\nParseCommandLine(): IV must be hexa, length must be 8 bytes for DES or 16 bytes for AES\n", 11)
						len = strlen(args[i+1] );
						size = ((*operation)->algorithm == AES)?AES_BLOCK_SIZE/8 : DES_BLOCK_SIZE/8;

						/* Se  ingresa key en formato NO hexa */
						if ( ( (*operation)->algorithm == AES && len == 16 ) || ( (*operation)->algorithm == DES && len == 8 ) )
							strncpy( (*operation)->iv, args[i+1], size );
						else
						{
							/*Cuando se ingresa key en formato Hexa*/
							hex2byte( (unsigned char *)(*operation)->iv, args[i+1],size);
						}
						i++;
						break;
		   			
		   	default:	FERROR("\nParseCommandLine(): Unknown command\n", 12)
		}
		
	}
	
	return 0;	
}

static int
isValidFormat( char *str, int algorithm )
{
	int i, error=FALSE;

	int len = strlen(str);
	if( ( algorithm == AES && len == 32 ) || ( algorithm == DES && len == 16 ) )
	{
		for( i=0 ; !error && i<len ; i++ )
		{
			char c = toupper(str[i]);
			if( !(c >='0'&& c<='9') &&  !(c >= 'A' && c <= 'F') )
				error = TRUE;
		}
		return !error;

	}else if ( ( algorithm == AES && len == 16 ) || ( algorithm == DES && len == 8 ) )
		return TRUE;
	else
		return FALSE;
}

static int 
isValidMode(char *str, int *modeCode)
{
	int i, found = FALSE;
	char *validModes[] = {"ECB", "CFB", "OFB", "CBC", "CFB1", "CFB8"};
	int modeCodes[] ={ECB, CFB, OFB, CBC, CFB1, CFB8};
	int dim = sizeof(validModes)/sizeof(validModes[0]);
	
	char *cad = strToUpper(str);
	for( i=0 ; !found && i< dim ; i++ )
		if( strcmp( cad, validModes[i]) == 0 )
		{
			found = TRUE;
			*modeCode = modeCodes[i];
		}

	free( cad );
	return found;
}

static char *
strToUpper( char *cad ){
	int i;
	char *ans = NULL;
	if( cad != NULL )
	{
		ans = strdup(cad);
		for( i=0 ; i<strlen(cad) ; i++ )
			ans[i] = toupper(cad[i]);
	}
	return ans;
}

static void
setDefaultValues( operationT *operation )
{
	memset( (*operation)->iv, 0x00, EVP_MAX_IV_LENGTH + 1);
	memset( (*operation)->key, 0x00, EVP_MAX_KEY_LENGTH + 1);
	(*operation)->algorithm = AES;
	(*operation)->mode = CBC;
	(*operation)->type = ENCRYPT;
	(*operation)->password = NULL;

	return;	
}

static int
isBMPfile( char *string )
{
	char *aux;
	aux = string;
	
	if( string == NULL )
		return FALSE;

	while( *aux != 0 && *aux != '.' )
		aux++;

	if( *aux == 0 )
		return FALSE;
	return strcmp( aux, ".bmp" ) || strcmp( aux, ".BMP" );
}


static int
GetCommandCode( char *cad )
{
	int i, dim;
	
	if( cad == NULL )
		return UNSET;

	dim = sizeof(validSwitches)/sizeof(validSwitches[0]);
	
	for( i=0 ; i<dim ; i++ )
		if( strcmp(cad, validSwitches[i]) == 0 )
			return i;
	return UNSET;
	
	
}

static void freeOperation( operationT *operation )
{
	if( (*operation)->password != NULL )
		free( (*operation)->password );

	free( *operation );
	*operation = NULL;
	return;
}
/*
static void
printHexa(unsigned char *buf, int len)
{
  int i, n;

  for( i=0,n=0 ; i<len ; i++ )
	  printf("%02x",buf[i]);
  printf("\n");
  return;
}
*/
 
// Usada para debug
void
ShowOperationData( blockEncrypterT b )
{	
	if( b != NULL )
	{
		operationT op = b->operation;
		if( op != NULL )
		{
			printf("\n\t type = %d\n", op->type);
			printf("\n\t password = %s\n", op->password);
			printf("\n\t key = %s\n",op->key);
			printf("\n\t iv = %s\n", op->iv);
			printf("\n\t algorithm = %d\n", op->algorithm);
			printf("\n\t mode = %d\n", op->mode);
		}
	}
	return;
}

